namespace NAct.Tests.Research
{
	using System;
	using System.Collections.Generic;
	using System.Linq;
	using System.Reflection;
	using System.Text;
	using NUnit.Framework;
	using NUnit.Framework.ExtensionMethods;
	using Patterns;

	[ TestFixture ]
	public class TypeConverterFixture
	{
		private IDictionary<Type, Func<string, object>> parsers;

		[ SetUp ]
		public void Set_up_test_context()
		{
			parsers = new Dictionary<Type, Func<string, object>>
			          {
			          	{ typeof( int ), input => int.Parse( input ) },
			          	{ typeof( int[] ), input => ( from value in input.Split( new[] { ',' } ) select int.Parse( value.Trim() ) ).ToArray() },
			          	{ typeof( float ), input => float.Parse( input ) },
			          	{ typeof( string ), input => input }
			          };
		}

		[ Test ]
		public void Should_be_able_to_inject_strings_into_methods()
		{
			MethodInfo method = GetMethodCalled( "DisplayName" );

			List<ParameterMapping> parameterMappings = GetParameters( method );

			parameterMappings[ 0 ].Value = parsers[ parameterMappings[ 0 ].Type ]( "Bob" );

			var output = new StringBuilder();
			var instance = InstanceOf<TestType>();
			instance.Logger = TestLoggingTo( output );

			method.Invoke( instance, GetParameterValues( parameterMappings ) );

			output.ToString().Should( Be.EqualTo( "Name is Bob" ) );
		}

		[ Test ]
		public void Should_be_able_to_inject_integers_into_methods()
		{
			MethodInfo method = GetMethodCalled( "DisplayAge" );

			List<ParameterMapping> parameterMappings = GetParameters( method );

			parameterMappings[ 0 ].Value = parsers[ parameterMappings[ 0 ].Type ]( "25" );

			var output = new StringBuilder();
			var instance = InstanceOf<TestType>();
			instance.Logger = TestLoggingTo( output );

			method.Invoke( instance, GetParameterValues( parameterMappings ) );

			output.ToString().Should( Be.EqualTo( "Age is 25" ) );
		}

		[ Test ]
		public void Should_be_able_to_inject_an_array_of_integers_into_a_methods()
		{
			MethodInfo method = GetMethodCalled( "DisplayIntegers" );

			List<ParameterMapping> parameterMappings = GetParameters( method );

			parameterMappings[ 0 ].Value = parsers[ parameterMappings[ 0 ].Type ]( "1, 2, 3, 4, 5" );

			var output = new StringBuilder();
			var instance = InstanceOf<TestType>();
			instance.Logger = TestLoggingTo( output );

			method.Invoke( instance, GetParameterValues( parameterMappings ) );

			output.ToString().Should( Be.EqualTo( "Values are:12345" ) );
		}

		[ Test ]
		public void Should_be_able_to_inject_a_float_into_a_methods()
		{
			MethodInfo method = GetMethodCalled( "DisplayFloat" );

			List<ParameterMapping> parameterMappings = GetParameters( method );

			parameterMappings[ 0 ].Value = parsers[ parameterMappings[ 0 ].Type ]( "3.1415926" );

			var output = new StringBuilder();
			var instance = InstanceOf<TestType>();
			instance.Logger = TestLoggingTo( output );

			method.Invoke( instance, GetParameterValues( parameterMappings ) );

			output.ToString().Should( Be.EqualTo( "Pi is 3.141593" ), "Pi is rounded" );
		}

		[ Test ]
		public void Should_be_able_to_inject_multiple_parameters()
		{
			MethodInfo method = GetMethodCalled( "DisplayUserInfo" );

			List<ParameterMapping> parameterMappings = GetParameters( method );

			IDictionary<string, string> paramValues = new Dictionary<string, string>
			                                          { { "forename", "Joe" }, { "surname", "Bloggs" }, { "age", "25" }, { "votes", "1, 2, 3, 4, 5" } };

			foreach ( ParameterMapping mapping in parameterMappings )
			{
				mapping.Value = parsers[ mapping.Type ]( paramValues[ mapping.Name ] );
			}

			var output = new StringBuilder();
			var instance = InstanceOf<TestType>();
			instance.Logger = TestLoggingTo( output );

			method.Invoke( instance, GetParameterValues( parameterMappings ) );

			output.ToString().Should( Be.EqualTo( "given a user called Joe Bloggs who is 25 years oldVoted: 12345" ) );
		}

		private static Action<string> TestLoggingTo( StringBuilder output )
		{
			return value => output.Append( value );
		}

		private static T InstanceOf<T>()
		{
			return (T) typeof( T ).GetConstructor( new Type[] { } ).Invoke( null );
		}

		private static MethodInfo GetMethodCalled( string methodName )
		{
			return ( from method in typeof( TestType ).GetMethods( BindingFlags.Instance | BindingFlags.Public )
			         where ( method.Name == methodName && method.ReturnType == typeof( void ) )
			         select method ).Single();
		}

		private static List<ParameterMapping> GetParameters( MethodInfo method )
		{
			return ( from parameter in method.GetParameters()
			         select new ParameterMapping { Name = parameter.Name, Type = parameter.ParameterType } ).ToList();
		}

		private static object[] GetParameterValues( IEnumerable<ParameterMapping> mappings )
		{
			return ( from mapping in mappings select mapping.Value ).ToArray();
		}
	}
}